AGC027E、ARC110E 等一类删除计数问题

Atcoder 好喜欢出诸如此类的题:你有一个由 ‘$a$’ ~ ‘$?$’ 构成的字符串,每次可以按某种规则删去一些再加上一些,问最终能形成多少种本质不同的字符串。

偏偏是我的克星。完全没得思路啊摔。仅有的两道 套路倒是相同的 qwq

$AGC027E$

规则:只有 $ab$,每次删 ‘$aa$’ 为 ‘$b$’ 或删 ‘$bb$’ 为 ‘$a$’。

如果我们将 $a$ 看作 $1$,将 $b$ 看作 $2$,在模 $3$ 意义下字符串不变。令 $p(s)$ 表示 $s$ 字符之和模 $3$。

一个字符串 $s$ 能规约到字符 $a$,当且仅当 $p(s) = p(a)$ 且 $s$ 中存在两个相邻的相同字符。(归纳证明:$|s| = 1$ 或 $2$ 时显然成立,否则你可以不断操作第一对相邻的相同字符。)

$t$ 能否被 $s$ 到达?容易贪心的想到「把 $s$ 分段后让 $t$ 的每个字符与对应段匹配」,且让分的段尽量靠前。于是就可以倒着 dp:$f_i$ 表示以 $[i, |s|]$ 为后缀有多少种分段方案数。

注意:最后可能剩下一段 $abababab$ 这样无法规约的段,但是它总能被前面部分通过调整规约顺序给化为最终串中的空串。

code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <bits/stdc++.h>
#define rep(i, x, y) for (int i = x; i <= y; i++)
using namespace std;

typedef long long ll;
const int mod = 1e9 + 7, N = 1e5 + 5;
int n;
int nxt[N][2];
char s[N];
ll f[N];

ll add(ll x, ll y) { return (x + y) % mod; }
ll sub(ll x, ll y) { return (x - y + mod) % mod; }

int main() {
scanf("%s", s + 1);
n = strlen(s + 1);
int ff = 1, sum = 0;
rep(i, 1, n - 1) if (s[i] == s[i + 1]) { ff = 0; break; }
if (ff) return puts("1"), 0;

f[n + 1] = 1;
nxt[n + 1][0] = nxt[n + 1][1] = nxt[n + 2][0] = nxt[n + 2][1] = n + 2;
for (int i = n; i; --i) {
(sum += s[i] == 'a' ? 1 : 2) %= 3;
nxt[i][0] = s[i] == 'a' ? i + 1 : (s[i + 1] == 'b' ? i + 2 : nxt[i + 2][0]); // 这一段 p = 1
nxt[i][1] = s[i] == 'b' ? i + 1 : (s[i + 1] == 'a' ? i + 2 : nxt[i + 2][1]); // p = 2
f[i] = add(f[nxt[i][0]], f[nxt[i][1]] + (sum == 0));
}
printf("%lld\n", sub(f[1], sum == 0));
return 0;
}

$ARC110E$

规则:只有 $ABC$,每次把两个相邻的不同字符替换为和它俩都不同的那个字符。

解题过程与上面那道完全相同。

如果我们将 $A$ 看作 $1$,$B$ 看作 $2$,$C$ 看作 $3$,在异或意义下字符串不变。令 $p(s)$ 表示 $s$ 异或和。

一个字符串 $s$ 能规约到字符 $a$,当且仅当 $p(s) = p(a)$ 且 $s$ 中存在两个相邻的不同字符。

code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <bits/stdc++.h>
#define rep(i, x, y) for (int i = x; i <= y; i++)
#define int long long
using namespace std;

typedef long long ll;
const int N = 1e6 + 10, mod = 1e9 + 7;
int n;
int a[N], lst[4], pre[N];
char s[N];
ll f[N];

ll add(ll x, ll y) { return (x + y) % mod; }
ll sub(ll x, ll y) { return (x - y + mod) % mod; }

signed main() {
cin >> n;
scanf("%s", s + 1);
bool ff = 1;
rep(i, 1, n - 1) if (s[i] != s[i + 1]) { ff = 0; break; }
rep(i, 1, n) a[i] = s[i] - 'A' + 1, pre[i] = (pre[i - 1] ^ a[i]);
if (ff) return puts("1"), 0;

for (int i = n; i >= 1; --i) {
f[i] = (pre[n] ^ pre[i]) == 0;
rep(j, 1, 3) {
f[i] = add(f[i], f[lst[j ^ pre[i]]]);
}
lst[pre[i]] = i;
}
ll ans = 0;
rep(i, 1, 3) ans = add(ans, f[lst[i]]);
printf("%lld\n", ans);
return 0;
}

总结

变中找不变。(苍 白 无 力)

找性质题有什么总结啊【暴躁】